import os
import hashlib
import logging
import sys
from datetime import datetime
from typing import Optional, Dict, Any
from netmiko import ConnectHandler
from sqlalchemy.orm import Session
from backend.models import Device, Backup, BackupStatus
from backend.config import settings
from backend.cleanup_service import CleanupService

# 配置详细的日志输出
# 创建日志目录
log_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'logs')
os.makedirs(log_dir, exist_ok=True)
log_file = os.path.join(log_dir, 'backup_debug.log')

try:
    logging.basicConfig(
        level=logging.DEBUG,
        format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
        handlers=[
            logging.StreamHandler(sys.stdout),  # 输出到控制台
            logging.FileHandler(log_file, encoding='utf-8', errors='replace')  # 输出到文件，使用UTF-8编码
        ]
    )
except Exception as e:
    # 如果文件日志失败，只使用控制台日志
    logging.basicConfig(
        level=logging.DEBUG,
        format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
        handlers=[
            logging.StreamHandler(sys.stdout),  # 输出到控制台
        ]
    )
    print(f"警告: 无法创建日志文件 {log_file}: {e}")

logger = logging.getLogger(__name__)

# 添加控制台输出函数
def console_log(message: str, level: str = "INFO"):
    """在控制台输出日志信息"""
    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    try:
        print(f"[{timestamp}] [{level}] {message}")
        sys.stdout.flush()  # 强制刷新输出
    except UnicodeEncodeError:
        # 如果遇到编码问题，使用安全的编码方式
        safe_message = message.encode('utf-8', errors='replace').decode('utf-8')
        print(f"[{timestamp}] [{level}] {safe_message}")
        sys.stdout.flush()

def safe_str(obj) -> str:
    """安全地将对象转换为字符串，处理编码问题"""
    try:
        return str(obj)
    except UnicodeEncodeError:
        return str(obj).encode('utf-8', errors='replace').decode('utf-8')

def sanitize_filename(filename: str) -> str:
    """清理文件名中的非法字符（Windows兼容）"""
    # Windows文件名非法字符: < > : " / \ | ? *
    # 还有一些保留名称: CON, PRN, AUX, NUL, COM1-9, LPT1-9
    illegal_chars = '<>:"/\\|?*'
    for char in illegal_chars:
        filename = filename.replace(char, '_')
    
    # 移除前后空格和点
    filename = filename.strip('. ')
    
    # 检查是否为Windows保留名称
    reserved_names = ['CON', 'PRN', 'AUX', 'NUL', 
                     'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9',
                     'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9']
    if filename.upper() in reserved_names:
        filename = f"device_{filename}"
    
    return filename if filename else 'unnamed_device'

class BackupService:
    """网络设备备份服务"""
    
    def __init__(self, db: Session):
        self.db = db
        self.backup_dir = settings.BACKUP_DIR
        os.makedirs(self.backup_dir, exist_ok=True)
    
    def get_device_config(self, device: Device) -> str:
        """连接设备并获取配置"""
        console_log(f"开始连接设备: {device.name} ({device.ip_address})", "INFO")
        console_log(f"设备类型: {device.device_type}", "INFO")
        
        # 映射设备类型到Netmiko支持的设备类型
        device_type_mapping = {
            'huawei_rsr77': 'huawei_vrp',
            'huawei_ar2220': 'huawei_vrp',
            'huawei_vrp': 'huawei_vrp',
            'huawei': 'huawei',
            'cisco_ios': 'cisco_ios',
            'cisco_xe': 'cisco_xe',
            'cisco_asa': 'cisco_asa',
            'h3c': 'hp_comware',
            'juniper': 'juniper',
            'arista_eos': 'arista_eos'
        }
        
        mapped_device_type = device_type_mapping.get(device.device_type, device.device_type)
        if mapped_device_type != device.device_type:
            console_log(f"设备类型映射: {device.device_type} -> {mapped_device_type}", "INFO")
        
        device_params = {
            'device_type': mapped_device_type,
            'host': device.ip_address,
            'username': device.username,
            'password': device.password,
            'port': device.port,
            'timeout': 30,
            'session_log': None,
        }
        
        # 如果有enable密码，添加到参数中
        if device.enable_password:
            device_params['secret'] = device.enable_password
            console_log("使用enable密码", "INFO")
        
        # 连接设备
        console_log("正在建立SSH连接...", "INFO")
        connection = ConnectHandler(**device_params)
        console_log("SSH连接成功", "SUCCESS")
        
        try:
            # 获取当前提示符
            current_prompt = connection.find_prompt()
            console_log(f"[DEBUG-001] 当前设备提示符: [{current_prompt}]", "INFO")
            logger.info(f"[DEBUG-001] 当前设备提示符: [{current_prompt}]")
            print(f"\n\n===== [DEBUG-001] 当前设备提示符: [{current_prompt}] =====\n\n", flush=True)
            
            # 检查设备类型以决定如何进入特权/系统视图模式
            device_type_lower = device.device_type.lower()
            is_huawei = 'huawei' in device_type_lower or 'vrp' in device_type_lower or 'ar2220' in device_type_lower
            
            # 如果提示符以 > 结尾，说明在用户模式，需要进入特权/系统视图模式
            if current_prompt.strip().endswith('>'):
                console_log(f"设备处于用户视图模式，尝试进入系统视图/特权模式...", "INFO")
                try:
                    if is_huawei:
                        # 华为设备：使用 system-view 命令进入系统视图
                        console_log("执行华为 'system-view' 命令...", "INFO")
                        result = connection.send_command('system-view', delay_factor=2, max_loops=50)
                        console_log(f"system-view 命令执行结果: {result[:100] if result else '无输出'}", "DEBUG")
                    else:
                        # Cisco/其他设备：使用 enable 命令进入特权模式
                        console_log("执行 'enable' 命令...", "INFO")
                        connection.enable()
                    
                    new_prompt = connection.find_prompt()
                    if new_prompt != current_prompt:
                        console_log(f"成功进入系统视图/特权模式，新提示符: {new_prompt}", "SUCCESS")
                        current_prompt = new_prompt
                    else:
                        console_log(f"命令执行完成，提示符未改变: {new_prompt}", "WARNING")
                        console_log("可能已在正确的模式，或设备不需要切换模式", "INFO")
                except Exception as e:
                    console_log(f"进入系统视图/特权模式失败: {safe_str(e)}", "WARNING")
                    console_log("将尝试在当前模式下执行命令", "INFO")
            
            # 更新用户模式检测（可能已经切换到系统视图了）
            is_user_mode = current_prompt.strip().endswith('>')
            
            console_log(f"[DEBUG-002] 设备类型: {device_type_lower}, is_huawei: {is_huawei}, is_user_mode: {is_user_mode}", "INFO")
            print(f"\n\n===== [DEBUG-002] is_huawei: {is_huawei}, is_user_mode: {is_user_mode} =====\n\n", flush=True)
            
            if is_huawei and is_user_mode:
                console_log(f"检测到华为设备处于用户模式（提示符: {current_prompt}）", "WARNING")
                
                # 先尝试获取帮助信息，看看设备支持哪些命令
                try:
                    console_log("探测设备支持的命令...", "DEBUG")
                    help_output = connection.send_command('?', delay_factor=2, max_loops=50)
                    if help_output and len(help_output) > 50:
                        console_log(f"设备帮助信息 (前200字符): {help_output[:200]}", "DEBUG")
                        # 检查是否支持 display 命令
                        if 'display' in help_output.lower():
                            console_log("设备支持 display 命令", "INFO")
                        else:
                            console_log("设备可能不支持 display 命令", "WARNING")
                except Exception as e:
                    console_log(f"获取帮助信息失败: {safe_str(e)}", "DEBUG")
                
                console_log("尝试进入系统视图模式...", "INFO")
                try:
                    # 华为设备：尝试进入系统视图
                    # 用户模式 <Huawei> -> 输入 system-view -> 系统视图 [Huawei]
                    sys_view_result = connection.send_command('system-view', delay_factor=2, max_loops=50)
                    new_prompt = connection.find_prompt()
                    
                    if '[' in new_prompt or not new_prompt.endswith('>'):
                        console_log(f"进入系统视图成功，新提示符: {new_prompt}", "SUCCESS")
                    else:
                        console_log(f"system-view 命令执行结果: {sys_view_result[:100] if sys_view_result else '无输出'}", "DEBUG")
                        console_log(f"提示符未改变，可能已在正确模式: {new_prompt}", "INFO")
                except Exception as e:
                    console_log(f"进入系统视图失败: {safe_str(e)}", "WARNING")
                    console_log("将尝试在当前模式下执行命令", "INFO")
            
            # 根据不同设备类型获取配置，添加错误处理
            console_log(f"开始尝试获取配置，设备类型: {device_type_lower}", "INFO")
            
            if 'cisco' in device_type_lower or 'ios' in device_type_lower:
                # Cisco IOS 设备
                console_log("使用Cisco IOS命令集", "INFO")
                config = self._try_commands(connection, [
                    'show running-config',
                    'show run',
                    'show config',
                    'show running-config all',
                    'show startup-config',
                    'show config running'
                ])
            elif 'huawei' in device_type_lower or 'vrp' in device_type_lower or 'ar2220' in device_type_lower:
                # 华为设备
                console_log("使用华为VRP命令集", "INFO")
                
                # 根据当前提示符决定命令集
                current_prompt_check = connection.find_prompt()
                prompt_lower = current_prompt_check.lower()
                
                # 检测到HBGC或N18014设备 - 这些模拟器设备使用show命令
                if any(x in prompt_lower for x in ['hbgc', 'n18014']):
                    console_log(f"检测到华为模拟器设备: {current_prompt_check}", "INFO")
                    console_log("该模拟器设备使用 'show' 命令代替 'display' 命令", "WARNING")
                    # 先尝试show命令
                    huawei_commands = [
                        'show running-config',
                        'show startup-config',
                        'show config',
                        'show run',
                        'show version'
                    ]
                else:
                    # 标准华为VRP设备
                    console_log(f"检测到华为VRP设备: {current_prompt_check}", "INFO")
                    huawei_commands = [
                        'display current-configuration',
                        'display saved-configuration',
                        'display current-config',
                        'display startup'
                    ]
                
                console_log(f"将尝试 {len(huawei_commands)} 个命令", "INFO")
                
                try:
                    # 先尝试标准命令集
                    config = self._try_commands(connection, huawei_commands)
                except Exception as e:
                    error_msg = safe_str(e)
                    # 如果display命令全部失败，且设备在用户模式，尝试show命令
                    if 'display' in huawei_commands[0] and 'unknown command' in error_msg.lower():
                        console_log("所有 'display' 命令失败，尝试使用 'show' 命令...", "WARNING")
                        huawei_commands_fallback = [
                            'show running-config',
                            'show startup-config',
                            'show config',
                            'show run',
                            'show version'
                        ]
                        config = self._try_commands(connection, huawei_commands_fallback)
                    else:
                        raise
            elif 'h3c' in device_type_lower or 'comware' in device_type_lower:
                # H3C 设备
                console_log("使用H3C Comware命令集", "INFO")
                config = self._try_commands(connection, [
                    'display current-configuration',
                    'display current-config',
                    'display saved-configuration',
                    'display current-configuration all',
                    'display startup-configuration',
                    'display config current'
                ])
            elif 'juniper' in device_type_lower or 'junos' in device_type_lower:
                # Juniper 设备
                console_log("使用Juniper JunOS命令集", "INFO")
                config = self._try_commands(connection, [
                    'show configuration',
                    'show config',
                    'show running-config',
                    'show configuration | display set',
                    'show configuration | no-more',
                    'show config | display set'
                ])
            elif 'arista' in device_type_lower or 'eos' in device_type_lower:
                # Arista 设备
                console_log("使用Arista EOS命令集", "INFO")
                config = self._try_commands(connection, [
                    'show running-config',
                    'show run',
                    'show config',
                    'show running-config all',
                    'show startup-config',
                    'show config running'
                ])
            else:
                # 默认尝试多种命令，包括更基础的命令
                console_log("使用通用命令集", "INFO")
                config = self._try_commands(connection, [
                    'show running-config',
                    'display current-configuration',
                    'show configuration',
                    'show config',
                    'show run',
                    'display current-config',
                    'show startup-config',
                    'display saved-configuration',
                    'show version',  # 基础命令，通常都支持
                    'show clock',    # 另一个基础命令
                    'show users',    # 用户命令
                    'show processes' # 进程命令
                ])
            
            if not config or config.strip() == '':
                console_log("获取到的配置为空", "ERROR")
                raise Exception("获取到的配置为空，可能是设备不支持该命令或权限不足")
            
            console_log(f"成功获取配置，长度: {len(config)} 字符", "SUCCESS")
            return config
            
        finally:
            console_log("断开SSH连接", "INFO")
            connection.disconnect()
    
    def _try_commands(self, connection, commands: list) -> str:
        """尝试多个命令，直到成功为止"""
        last_error = None
        all_errors = []
        successful_commands = []
        
        # 首先尝试进入特权模式（如果需要）
        try:
            if not connection.check_enable_mode():
                console_log("尝试进入特权模式...", "INFO")
                logger.info("尝试进入特权模式...")
                connection.enable()
                console_log("成功进入特权模式", "SUCCESS")
                logger.info("成功进入特权模式")
            else:
                console_log("已在特权模式", "INFO")
        except Exception as e:
            error_str = safe_str(e)
            console_log(f"无法进入特权模式: {error_str}", "WARNING")
            logger.warning(f"无法进入特权模式: {error_str}")
        
        # 检查设备提示符，判断设备类型
        try:
            prompt = connection.find_prompt()
            console_log(f"设备提示符: {prompt}", "INFO")
            logger.info(f"设备提示符: {prompt}")
            
            # 根据提示符调整命令策略
            if '>' in prompt and not '#' in prompt:
                console_log("设备可能处于用户模式，尝试更多基础命令", "WARNING")
                logger.warning("设备可能处于用户模式，尝试更多基础命令")
            
            # 特殊华为设备处理
            if 'HBGC' in prompt or 'RSR77' in prompt or 'AR2220' in prompt or 'AR2200' in prompt:
                console_log("检测到特殊华为设备 (HBGC/RSR77/AR2220)，使用特殊命令集", "INFO")
                logger.info("检测到特殊华为设备 (HBGC/RSR77/AR2220)，使用特殊命令集")
                # 为特殊华为设备添加更多命令
                commands.extend([
                    'display current-configuration',
                    'display current-config',
                    'display saved-configuration',
                    'display current-configuration all',
                    'display startup-configuration',
                    'display config current',
                    'display version',
                    'display clock'
                ])
            
            # 华为特权模式设备处理
            if 'Huawei' in prompt and ('[' in prompt or '<' in prompt):
                console_log("检测到华为特权模式设备，使用特权模式命令集", "INFO")
                logger.info("检测到华为特权模式设备，使用特权模式命令集")
                # 为华为特权模式设备添加更多命令
                commands.extend([
                    'display current-configuration',
                    'display current-config',
                    'display saved-configuration',
                    'display current-configuration all',
                    'display startup-configuration',
                    'display config current',
                    'display version',
                    'display clock'
                ])
        except Exception as e:
            error_str = safe_str(e)
            console_log(f"无法获取设备提示符: {error_str}", "WARNING")
            logger.warning(f"无法获取设备提示符: {error_str}")
        
        for i, command in enumerate(commands):
            try:
                console_log(f"尝试命令 {i+1}/{len(commands)}: {command}", "INFO")
                logger.info(f"尝试命令 {i+1}/{len(commands)}: {command}")
                
                # 发送命令并获取结果，增加延迟因子
                result = connection.send_command(command, delay_factor=3, max_loops=100)
                
                # 安全处理编码问题 - 处理可能的Unicode字符
                if result:
                    try:
                        # 确保结果是字符串类型
                        if not isinstance(result, str):
                            result = str(result)
                        
                        # 处理可能的编码问题
                        if isinstance(result, bytes):
                            result = result.decode('utf-8', errors='replace')
                        else:
                            # 对于已经是字符串的情况，确保能安全处理Unicode字符
                            result = result.encode('utf-8', errors='replace').decode('utf-8')
                    except (UnicodeEncodeError, UnicodeDecodeError, AttributeError):
                        # 如果所有编码处理都失败，使用最基本的转换
                        result = str(result)
                console_log(f"命令 '{command}' 返回结果长度: {len(result) if result else 0}", "INFO")
                logger.info(f"命令 '{command}' 返回结果长度: {len(result) if result else 0}")
                
                # 记录原始响应用于调试（显示完整内容）
                if result:
                    console_log(f"命令 '{command}' 原始响应(长度:{len(result)}): {repr(result)}", "DEBUG")
                    logger.debug(f"命令 '{command}' 原始响应(长度:{len(result)}): {result}")
                    # 如果结果很短，显示完整内容
                    if len(result) < 200:
                        console_log(f"完整响应: [{result}]", "DEBUG")
                else:
                    console_log(f"命令 '{command}' 返回空结果", "WARNING")
                
                # 检查是否包含错误信息 - 更精确的错误检测
                result_lower = result.lower() if result else ""
                
                # 华为设备特有的错误模式
                huawei_error_patterns = [
                    '% unknown command', '% invalid command', '% command not found',
                    '% unrecognized command', '% syntax error', '% bad command',
                    '% incomplete command', '% ambiguous command',
                    '% invalid input', '% command not recognized'
                ]
                
                # 通用错误模式
                general_error_patterns = [
                    'error:', 'failed:', 'denied', 'permission denied'
                ]
                
                # 检查华为设备错误模式（更严格）
                found_huawei_errors = [pattern for pattern in huawei_error_patterns if pattern in result_lower]
                
                # 检查通用错误模式
                found_general_errors = [pattern for pattern in general_error_patterns if pattern in result_lower]
                
                # 只有当错误模式存在且结果很短（小于100字符）时才认为是真正的错误
                # 如果结果很长，说明可能有配置内容，错误信息可能只是警告
                is_error = (found_huawei_errors or found_general_errors) and len(result.strip()) < 100
                
                console_log(f"命令 '{command}' 错误检测: 发现华为错误={found_huawei_errors}, 发现通用错误={found_general_errors}, 结果长度={len(result.strip())}, 判断为错误={is_error}", "DEBUG")
                
                if is_error:
                    error_msg = f"命令 '{command}' 失败: 发现错误模式 {found_huawei_errors + found_general_errors}"
                    if result:
                        error_msg += f"\n设备响应: {result[:300]}..."
                    console_log(f"[错误] {error_msg}", "ERROR")
                    last_error = error_msg
                    all_errors.append(error_msg)
                    logger.warning(error_msg)
                    continue
                
                # 检查结果是否为空
                if not result or not result.strip():
                    error_msg = f"命令 '{command}' 返回空结果"
                    last_error = error_msg
                    all_errors.append(error_msg)
                    logger.warning(error_msg)
                    continue
                
                # 检查是否只包含提示符
                stripped_result = result.strip()
                if (stripped_result.endswith('#') or stripped_result.endswith('>') or 
                    stripped_result.endswith('$') or len(stripped_result) < 20):
                    error_msg = f"命令 '{command}' 返回结果过短或只包含提示符: {stripped_result}"
                    last_error = error_msg
                    all_errors.append(error_msg)
                    logger.warning(error_msg)
                    continue
                
                # 检查是否包含配置内容的关键词
                config_keywords = [
                    'interface', 'router', 'hostname', 'ip address', 'vlan',
                    'access-list', 'route', 'ospf', 'bgp', 'eigrp',
                    'spanning-tree', 'switchport', 'trunk', 'access',
                    'version', 'service', 'logging', 'ntp', 'snmp'
                ]
                
                has_config_content = any(keyword in result_lower for keyword in config_keywords)
                if not has_config_content and len(stripped_result) < 100:
                    error_msg = f"命令 '{command}' 返回结果不包含配置内容: {stripped_result[:100]}..."
                    last_error = error_msg
                    all_errors.append(error_msg)
                    logger.warning(error_msg)
                    continue
                
                # 成功获取配置
                console_log(f"[成功] 命令 '{command}' 成功，返回配置长度: {len(result)}", "SUCCESS")
                logger.info(f"命令 '{command}' 成功，返回配置长度: {len(result)}")
                successful_commands.append({
                    'command': command,
                    'length': len(result),
                    'preview': result[:200]
                })
                return result
                    
            except Exception as e:
                # 安全处理异常信息，避免编码问题
                error_str = safe_str(e)
                error_msg = f"执行命令 '{command}' 异常: {error_str}"
                last_error = error_msg
                all_errors.append(error_msg)
                logger.error(error_msg)
                continue
        
        # 如果所有命令都失败了，提供详细的错误信息和建议
        error_summary = f"所有 {len(commands)} 个命令都失败了:\n\n"
        
        # 显示每个命令的详细错误
        for i, error in enumerate(all_errors, 1):
            error_summary += f"{i}. {error}\n\n"
        
        # 显示成功的命令（如果有）
        if successful_commands:
            error_summary += f"部分命令有响应但不符合要求:\n"
            for cmd in successful_commands:
                error_summary += f"- {cmd['command']}: {cmd['length']} 字符\n"
            error_summary += "\n"
        
        # 提供具体的解决建议
        error_summary += "解决建议:\n"
        error_summary += "1. 检查设备类型是否正确（尝试 'generic' 类型）\n"
        error_summary += "2. 确认用户权限是否足够（需要特权模式权限）\n"
        error_summary += "3. 手动登录设备验证命令是否可用\n"
        error_summary += "4. 检查设备是否支持SSH配置备份\n"
        error_summary += "5. 尝试使用 'debug' 功能查看详细命令执行过程\n"
        error_summary += "6. 检查设备是否处于正确的配置模式\n"
        
        # 添加设备特定的建议
        if any('cisco' in cmd.lower() for cmd in commands):
            error_summary += "\nCisco设备特殊建议:\n"
            error_summary += "- 确保设备支持 'show running-config' 命令\n"
            error_summary += "- 检查是否在特权模式下执行\n"
            error_summary += "- 尝试 'show run' 或 'show config' 命令\n"
        
        if any('display' in cmd.lower() for cmd in commands):
            error_summary += "\n华为/H3C设备特殊建议:\n"
            error_summary += "- 确保设备支持 'display current-configuration' 命令\n"
            error_summary += "- 检查用户是否有配置查看权限\n"
            error_summary += "- 尝试 'display current-config' 命令\n"
        
        raise Exception(error_summary)
    
    def calculate_md5(self, content: str) -> str:
        """计算配置内容的MD5哈希"""
        return hashlib.md5(content.encode('utf-8')).hexdigest()
    
    def save_config_to_file(self, device: Device, config: str) -> Dict[str, Any]:
        """保存配置到文件"""
        # 清理设备名称，避免Windows文件名非法字符
        safe_device_name = sanitize_filename(device.name)
        console_log(f"使用安全文件名: {safe_device_name} (原名: {device.name})", "DEBUG")
        
        # 创建设备专属目录
        device_dir = os.path.join(self.backup_dir, safe_device_name)
        console_log(f"创建备份目录: {device_dir}", "DEBUG")
        os.makedirs(device_dir, exist_ok=True)
        
        # 生成文件名：设备名_时间戳.txt
        timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
        filename = f"{safe_device_name}_{timestamp}.txt"
        filepath = os.path.join(device_dir, filename)
        console_log(f"备份文件路径: {filepath}", "DEBUG")
        
        # 保存文件
        with open(filepath, 'w', encoding='utf-8') as f:
            f.write(config)
        
        # 获取文件大小
        file_size = os.path.getsize(filepath)
        
        # 计算MD5
        config_hash = self.calculate_md5(config)
        
        return {
            'filename': filename,
            'filepath': filepath,
            'file_size': file_size,
            'config_hash': config_hash
        }
    
    def backup_device(self, device_id: int, is_manual: bool = False) -> Backup:
        """执行设备备份"""
        device = self.db.query(Device).filter(Device.id == device_id).first()
        
        if not device:
            raise ValueError(f"设备ID {device_id} 不存在")
        
        backup = Backup(
            device_id=device_id,
            status=BackupStatus.IN_PROGRESS,
            is_manual=is_manual
        )
        self.db.add(backup)
        self.db.commit()
        
        try:
            # 获取设备配置
            config = self.get_device_config(device)
            
            # 保存配置到文件
            file_info = self.save_config_to_file(device, config)
            
            # 更新备份记录
            backup.filename = file_info['filename']
            backup.filepath = file_info['filepath']
            backup.file_size = file_info['file_size']
            backup.config_hash = file_info['config_hash']
            backup.status = BackupStatus.SUCCESS
            backup.backup_time = datetime.utcnow()
            
            self.db.commit()
            self.db.refresh(backup)
            
            # 备份成功后，清理旧备份
            try:
                cleanup_service = CleanupService(self.db)
                cleanup_result = cleanup_service.cleanup_device_backups(device_id)
                if cleanup_result["success"] and cleanup_result["cleaned_count"] > 0:
                    logger.info(f"设备 {device.name} 自动清理了 {cleanup_result['cleaned_count']} 个旧备份")
            except Exception as e:
                logger.warning(f"自动清理旧备份失败: {str(e)}")
            
            return backup
            
        except Exception as e:
            # 备份失败，记录错误
            # 生成失败时的文件名和路径（使用安全文件名）
            safe_device_name = sanitize_filename(device.name)
            timestamp = datetime.utcnow().strftime('%Y%m%d_%H%M%S')
            failed_filename = f"{safe_device_name}_{timestamp}_FAILED.txt"
            failed_filepath = os.path.join(self.backup_dir, "failed", failed_filename)
            
            # 确保失败目录存在
            os.makedirs(os.path.dirname(failed_filepath), exist_ok=True)
            
            # 创建失败记录文件
            try:
                with open(failed_filepath, 'w', encoding='utf-8') as f:
                    f.write(f"备份失败时间: {datetime.utcnow()}\n")
                    f.write(f"设备名称: {device.name}\n")
                    f.write(f"设备IP: {device.ip_address}\n")
                    f.write(f"错误信息: {str(e)}\n")
            except Exception as file_error:
                logger.warning(f"创建失败记录文件失败: {str(file_error)}")
                failed_filepath = None
                failed_filename = f"{safe_device_name}_{timestamp}_FAILED.txt"
            
            # 更新备份记录
            backup.filename = failed_filename
            backup.filepath = failed_filepath or ""
            backup.file_size = os.path.getsize(failed_filepath) if failed_filepath and os.path.exists(failed_filepath) else 0
            backup.status = BackupStatus.FAILED
            backup.error_message = str(e)
            backup.backup_time = datetime.utcnow()
            
            self.db.commit()
            self.db.refresh(backup)
            
            raise Exception(f"备份失败: {str(e)}")
    
    def get_config_content(self, backup_id: int) -> str:
        """读取备份配置内容"""
        backup = self.db.query(Backup).filter(Backup.id == backup_id).first()
        
        if not backup:
            raise ValueError(f"备份ID {backup_id} 不存在")
        
        if not os.path.exists(backup.filepath):
            raise FileNotFoundError(f"配置文件不存在: {backup.filepath}")
        
        with open(backup.filepath, 'r', encoding='utf-8') as f:
            return f.read()
    
    def compare_configs(self, backup_id1: int, backup_id2: int) -> Dict[str, Any]:
        """比较两个备份配置的差异"""
        import difflib
        
        config1 = self.get_config_content(backup_id1)
        config2 = self.get_config_content(backup_id2)
        
        # 生成差异
        diff = difflib.unified_diff(
            config1.splitlines(keepends=True),
            config2.splitlines(keepends=True),
            lineterm='',
            n=3
        )
        
        diff_text = ''.join(diff)
        
        return {
            'backup_id1': backup_id1,
            'backup_id2': backup_id2,
            'diff': diff_text,
            'has_changes': len(diff_text) > 0
        }

